using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


	using IdGenerator = org.neo4j.kernel.impl.nioneo.store.IdGenerator;
	using IdGeneratorImpl = org.neo4j.kernel.impl.nioneo.store.IdGeneratorImpl;
	using StoreFailureException = org.neo4j.kernel.impl.nioneo.store.StoreFailureException;

	using Test = junit.framework.Test;
	using TestCase = junit.framework.TestCase;
	using TestSuite = junit.framework.TestSuite;

	public class TestIdGenerator : TestCase
	{

		public TestIdGenerator(string testName) : base(testName)
		{
		}

		static void Main(string[] args)
		{
			junit.textui.TestRunner.run(suite());
		}

		public static Test suite()
		{
			TestSuite suite = new TestSuite(typeof(TestIdGenerator));
			return suite;
		}

		public virtual void setUp()
		{
		}

		public virtual void tearDown()
		{
		}

		public virtual void testCreateIdGenerator()
		{
			try
			{
				IdGeneratorImpl.createGenerator(null);
				fail("Null filename should throw exception");
			}
			catch (IllegalArgumentException e)
			{
			} // good
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				new IdGeneratorImpl("testIdGenerator.id", 0).Close();
				fail("Zero grab size should throw exception");
			}
			catch (IllegalArgumentException e)
			{
			} // good
			try
			{
				new IdGeneratorImpl("testIdGenerator.id", -1).Close();
				fail("Negative grab size should throw exception");
			}
			catch (IllegalArgumentException e)
			{
			} // good

			try
			{
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 1008);
				try
				{
					IdGeneratorImpl.createGenerator("testIdGenerator.id");
					fail("Creating a id generator with existing file name " + "should throw exception");
				}
				catch (IllegalStateException e)
				{
				} // good
				idGenerator.Close();
			// verify that id generator is ok
				FileChannel fileChannel = new FileInputStream("testIdGenerator.id").getChannel();
				ByteBuffer buffer = ByteBuffer.allocate(9);
				assertEquals(9, fileChannel.read(buffer));
				buffer.flip();
				assertEquals((sbyte) 0, buffer.get());
				assertEquals(0, buffer.getLong());
				buffer.flip();
				int readCount = fileChannel.read(buffer);
				if (readCount != -1 && readCount != 0)
				{
					fail("Id generator header not ok read 9 + " + readCount + " bytes from file");
				}
				fileChannel.Close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
				fail("" + e);
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}

		public virtual void testStickyGenerator()
		{
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGen = new IdGeneratorImpl("testIdGenerator.id", 3);
				try
				{
					new IdGeneratorImpl("testIdGenerator.id", 3);
					fail("Opening sticky id generator should throw exception");
				}
				catch (StoreFailureException e)
				{ // good
				}
				idGen.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}

		public virtual void testNextId()
		{
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 3);
				for (int i = 0; i < 7; i++)
				{
					assertEquals(i, idGenerator.nextId());
				}
				idGenerator.freeId(1);
				idGenerator.freeId(3);
				idGenerator.freeId(5);
				assertEquals(7, idGenerator.nextId());
				idGenerator.freeId(6);
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 5);
				idGenerator.freeId(2);
				idGenerator.freeId(4);
				assertEquals(1, idGenerator.nextId());
				idGenerator.freeId(1);
				assertEquals(3, idGenerator.nextId());
				idGenerator.freeId(3);
				assertEquals(5, idGenerator.nextId());
				idGenerator.freeId(5);
				assertEquals(6, idGenerator.nextId());
				idGenerator.freeId(6);
				assertEquals(8, idGenerator.nextId());
				idGenerator.freeId(8);
				assertEquals(9, idGenerator.nextId());
				idGenerator.freeId(9);
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 3);
				assertEquals(2, idGenerator.nextId());
				assertEquals(4, idGenerator.nextId());
				assertEquals(1, idGenerator.nextId());
				assertEquals(3, idGenerator.nextId());
				assertEquals(5, idGenerator.nextId());
				assertEquals(6, idGenerator.nextId());
				assertEquals(8, idGenerator.nextId());
				assertEquals(9, idGenerator.nextId());
				assertEquals(10, idGenerator.nextId());
				assertEquals(11, idGenerator.nextId());
				idGenerator.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}

		public virtual void testFreeId()
		{
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 3);
				for (int i = 0; i < 7; i++)
				{
					assertEquals(i, idGenerator.nextId());
				}
				try
				{
					idGenerator.freeId(-1);
					fail("Negative id should throw exception");
				}
				catch (IllegalArgumentException e)
				{ // good
				}
				try
				{
					idGenerator.freeId(7);
					fail("Greater id than ever returned should throw exception");
				}
				catch (IllegalArgumentException e)
				{ // good
				}
				for (int i = 0; i < 7; i++)
				{
					idGenerator.freeId(i);
				}
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 2);
				assertEquals(0, idGenerator.nextId());
				assertEquals(1, idGenerator.nextId());
				assertEquals(2, idGenerator.nextId());
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 2);
				assertEquals(4, idGenerator.nextId());
				assertEquals(5, idGenerator.nextId());
				assertEquals(6, idGenerator.nextId());
				assertEquals(3, idGenerator.nextId());
				idGenerator.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}

		public virtual void testClose()
		{
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 2);
				idGenerator.Close();
				try
				{
					idGenerator.nextId();
					fail("nextId after close should throw exception");
				}
				catch (IllegalStateException e)
				{ // good
				}
				try
				{
					idGenerator.freeId(0);
					fail("freeId after close should throw exception");
				}
				catch (IllegalArgumentException e)
				{ // good
				}
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 2);
				assertEquals(0, idGenerator.nextId());
				assertEquals(1, idGenerator.nextId());
				assertEquals(2, idGenerator.nextId());
				idGenerator.Close();
				try
				{
					idGenerator.nextId();
					fail("nextId after close should throw exception");
				}
				catch (IllegalStateException e)
				{ // good
				}
				try
				{
					idGenerator.freeId(0);
					fail("freeId after close should throw exception");
				}
				catch (IllegalArgumentException e)
				{ // good
				}
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}

		public virtual void testOddAndEvenWorstCase()
		{
			int capacity = 1024 * 8 + 1;
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 128);
				for (int i = 0; i < capacity; i++)
				{
					idGenerator.nextId();
				}
				Map<long, object> freedIds = new Dictionary<long, object>();
				for (long i = 1; i < capacity; i += 2)
				{
					idGenerator.freeId(i);
					freedIds.put(i, this);
				}
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 2000);
				long oldId = -1;
				for (int i = 0; i < capacity - 1; i += 2)
				{
					long id = idGenerator.nextId();
					if (freedIds.Remove(id) == null)
					{
						throw new RuntimeException("Id=" + id + " prevId=" + oldId + " list.size()=" + freedIds.size());
					}
					oldId = id;
				}
				assertTrue(freedIds.values().size() == 0);
				idGenerator.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 128);
				for (int i = 0; i < capacity; i++)
				{
					idGenerator.nextId();
				}
				Map<long, object> freedIds = new Dictionary<long, object>();
				for (long i = 0; i < capacity; i += 2)
				{
					idGenerator.freeId(i);
					freedIds.put(i, this);
				}
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 2000);
				for (int i = 0; i < capacity; i += 2)
				{
					assertEquals(this, freedIds.Remove(idGenerator.nextId()));
				}
				assertEquals(0, freedIds.values().size());
				idGenerator.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}

		public virtual void testRandomTest()
		{
			int numberOfCloses = 0;
			System.Random random = new System.Random(System.currentTimeMillis());
			int capacity = random.Next(1024) + 1024;
			int grabSize = random.Next(128) + 128;
			IdGeneratorImpl.createGenerator("testIdGenerator.id");
			IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", grabSize);
			List<long> idsTaken = new List<long>();
			float releaseIndex = 0.25f;
			float closeIndex = 0.05f;
			int currentIdCount = 0;
			try
			{
				while (currentIdCount < capacity)
				{
					float rIndex = random.nextFloat();
					if (rIndex < releaseIndex && currentIdCount > 0)
					{
						idGenerator.freeId(idsTaken.Remove(random.Next(currentIdCount)).intValue());
						currentIdCount--;
					}
					else
					{
						idsTaken.Add(idGenerator.nextId());
						currentIdCount++;
					}
					if (rIndex > (1.0f - closeIndex) || rIndex < closeIndex)
					{
						idGenerator.Close();
						grabSize = random.Next(128) + 128;
						idGenerator = new IdGeneratorImpl("testIdGenerator.id", grabSize);
						numberOfCloses++;
					}
				}
				idGenerator.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}

		}

		public virtual void testUnsignedId()
		{
			try
			{
				IdGeneratorImpl.createGenerator("testIdGenerator.id");
				IdGenerator idGenerator = new IdGeneratorImpl("testIdGenerator.id", 1);
				idGenerator.setHighId(4294967293l);
				long id = idGenerator.nextId();
				assertEquals(4294967293l, id);
				idGenerator.freeId(id);
				try
				{
					idGenerator.nextId();
				}
				catch (StoreFailureException e)
				{ // good, capacity exceeded
				}
				idGenerator.Close();
				idGenerator = new IdGeneratorImpl("testIdGenerator.id", 1);
				assertEquals(4294967294l, idGenerator.getHighId());
				id = idGenerator.nextId();
				assertEquals(4294967293l, id);
				try
				{
					idGenerator.nextId();
				}
				catch (StoreFailureException e)
				{ // good, capacity exceeded
				}
				idGenerator.Close();
			}
			finally
			{
				File file = new File("testIdGenerator.id");
				if (file.exists())
				{
					assertTrue(file.delete());
				}
			}
		}
	}
}